package com.hk.core.web;

import com.hk.commons.util.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import static com.hk.commons.util.Constants.*;

/**
 * web相关的工具类
 *
 * @author kevin
 * @date 2017年9月22日下午2:51:02
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public abstract class Webs {

    /**
     * 获取request对象
     *
     * @return HttpServletRequest
     */
    @Nullable
    public static HttpServletRequest getHttpServletRequest() {
        var requestAttribute = getRequestAttribute();
        return Objects.isNull(requestAttribute) ? null : requestAttribute.getRequest();
    }

    /**
     * 获取response对象
     *
     * @return HttpServletResponse
     */
    @Nullable
    public static HttpServletResponse getHttpServletResponse() {
        var requestAttribute = getRequestAttribute();
        return Objects.isNull(requestAttribute) ? null : requestAttribute.getResponse();
    }

    /**
     * 从request请求域中获取属性值
     *
     * @param name name
     * @return request attribute by give name.
     */
    @Nullable
    public static <T> T getAttributeFromRequest(String name, Class<T> clazz) {
        return getAttribute(name, RequestAttributes.SCOPE_REQUEST, clazz);
    }

    /**
     * 从Session请求域中获取属性值
     *
     * @param name name
     * @return session value by give name
     */
    @Nullable
    public static <T> T getAttributeFromSession(String name, Class<T> clazz) {
        return getAttribute(name, RequestAttributes.SCOPE_SESSION, clazz);
    }

    /**
     * 从Session请求域中设置属性值
     *
     * @param name  name
     * @param value value
     */
    public static void setAttributeFromSession(String name, Object value) {
        setAttributeFromSession(name, value, false);
    }

    /**
     * 从Session请求域中设置属性值
     *
     * @param name   name
     * @param value  value
     * @param create Whether to create session
     */
    public static void setAttributeFromSession(String name, Object value, boolean create) {
        var request = getHttpServletRequest();
        if (Objects.nonNull(request)) {
            var session = request.getSession(create);
            if (Objects.nonNull(session)) {
                session.setAttribute(name, value);
            }
        }
    }

    /**
     * 从Session请求域中设置属性值
     *
     * @param name name
     */
    public static void removeAttributeFromSession(String name) {
        var requestAttribute = getRequestAttribute();
        if (Objects.nonNull(requestAttribute)) {
            requestAttribute.removeAttribute(name, RequestAttributes.SCOPE_SESSION);
        }
    }

    /**
     * 获取属性值
     *
     * @param name  name
     * @param scope scope
     * @return Value
     */
    @Nullable
    public static <T> T getAttribute(String name, int scope, Class<T> clazz) throws ClassCastException {
        var requestAttribute = getRequestAttribute();
        if (Objects.isNull(requestAttribute)) {
            return null;
        }
        var value = requestAttribute.getAttribute(name, scope);
        return Objects.isNull(value) ? null : clazz.cast(value);
    }

    public static ServletRequestAttributes getRequestAttribute() {
        return (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    }

    /**
     * 是否为ajax请求
     *
     * @param request request
     * @return true if Request Header contains X-Requested-With
     */
    public static boolean isAjax(HttpServletRequest request) {
        return StringUtils.isNotEmpty(request.getHeader("X-Requested-With"));
    }

//    /**
//     * 是否为ajax请求
//     *
//     * @return true if Request Header contains X-Requested-With
//     */
//    public static boolean isAjax() {
//        return isAjax(getHttpServletRequest());
//    }

    /**
     * 是否是微信浏览器请求
     *
     * @param request request
     * @return true if Request Header contains MicroMessenger
     */
    public static boolean isWeiXin(HttpServletRequest request) {
        return StringUtils.contains(request.getHeader(HttpHeaders.USER_AGENT), "MicroMessenger");
    }

    /**
     * 判断是否是支付宝发出的请求
     *
     * @param request request
     * @return true or false
     */
    public static boolean isAliPay(HttpServletRequest request) {
        return StringUtils.contains(request.getHeader(HttpHeaders.USER_AGENT), "AlipayClient");
    }

    /**
     * 判断是否是 android 发出的请求
     *
     * @param request request
     * @return true or false
     */
    public static boolean isAndroid(HttpServletRequest request) {
        return StringUtils.contains(request.getHeader(HttpHeaders.USER_AGENT), "Android");
    }

    /**
     * 判断是否手机端请求
     */
    public static boolean isMobile(HttpServletRequest request) {
        return isAndroid(request) || isIPhone(request);
    }

    /**
     * 判断是否是 苹果手机应用发送的请求
     *
     * @param request request
     * @return true or false
     */
    public static boolean isIPhone(HttpServletRequest request) {
        return StringUtils.contains(request.getHeader(HttpHeaders.USER_AGENT), "iPhone");
    }

    /**
     * 获取所有请求参数
     *
     * @param request request
     * @return 请求参数
     */
    public static Map<String, String> getRequestParam(HttpServletRequest request) {
        var requestParameterMap = request.getParameterMap();
        var result = new HashMap<String, String>();
        for (var entry : requestParameterMap.entrySet()) {
            var value = StringUtils.arrayToCommaDelimitedString(entry.getValue());
            if (StringUtils.isNotEmpty(value)) {
                result.put(entry.getKey(), value);
            }
        }
        return result;
    }

    public static void removeAllSessionAttribute(HttpServletRequest request) {
        var session = request.getSession(false);
        if (Objects.nonNull(session)) {
            var attributeNames = session.getAttributeNames();
            while (attributeNames.hasMoreElements()) {
                session.removeAttribute(attributeNames.nextElement());
            }
        }
    }

    /**
     * 将 params 构建为 url 的参数
     *
     * @param url    url
     * @param params params
     */
    public static String buildUrl(String url, Map<String, ?> params) {
        if (CollectionUtils.isEmpty(params)) {
            return url;
        }
        var sb = new StringBuffer();
        params.forEach((key, value) -> {
            if (Objects.nonNull(value)) {
                if (!sb.isEmpty()) {
                    sb.append("&");
                }
                sb.append(key).append("=").append(ConverterUtils.defaultConvert(value, String.class));
            }
        });
        if (!sb.isEmpty() && !StringUtils.endsWith(url, "?")) {
            url = STR."\{url}?";
        }
        return sb.insert(0, url).toString();
    }

    /**
     * 文件下载或预览
     *
     * @param fileName fileName
     * @param body     body
     * @return ResponseEntity
     */
    public static ResponseEntity<InputStreamResource> toResponseEntity(String fileName, byte[] body) {
        return toResponseEntity(fileName, new ByteArrayResource(body));
    }

    /**
     * <pre>
     * 下载或预览文件
     *  当文件不是图片时，仅下载
     *  当文件为图片且 download 为 false 时，预览图片
     * </pre>
     */
    @SneakyThrows
    public static ResponseEntity<InputStreamResource> toResponseEntity(Path path, boolean download) {
        return toResponseEntity(path.toFile(), download);
    }

    /**
     * <pre>
     * 下载或预览文件
     *  当文件不是图片时，仅下载
     *  当文件为图片且 download 为 false 时，预览图片
     * </pre>
     */
    @SneakyThrows
    public static ResponseEntity<InputStreamResource> toResponseEntity(File file, boolean download) {
        AssertUtils.isTrue(file.isFile(), "Parameter file is not a file.");
        String fileName = null;
        MediaType mediaType;
        if (download || !FileUtils.isImage(file.getName())) {
            fileName = file.getName();
            mediaType = MediaType.APPLICATION_OCTET_STREAM;
        } else {
            mediaType = getMediaType(file.getName());
        }
        return toResponseEntity(fileName, mediaType, file.length(),
                new InputStreamResource(org.apache.commons.io.FileUtils.openInputStream(file)));
    }

    public static MediaType getMediaType(String fileName) {
        return MediaTypeFactory.getMediaType(fileName).orElse(MediaType.APPLICATION_OCTET_STREAM);
    }

    /**
     * 文件下载或预览
     *
     * @param fileName fileName
     * @param resource resource
     * @return ResponseEntity
     */
    @SneakyThrows(value = {IOException.class})
    public static ResponseEntity<InputStreamResource> toResponseEntity(String fileName, Resource resource) {
        return toResponseEntity(fileName, resource.contentLength(), resource.getInputStream());
    }

    /**
     * 文件下载或预览
     *
     * @param fileName fileName
     * @param url      url
     * @return ResponseEntity
     */
    @SneakyThrows(value = {IOException.class})
    public static ResponseEntity<InputStreamResource> toResponseEntity(String fileName, URL url) {
        var connection = url.openConnection();
        return toResponseEntity(fileName, connection.getContentLength(), connection.getInputStream());
    }

    /**
     * 预览图片
     */
    @SneakyThrows
    public static ResponseEntity<Resource> toPreviewResponseEntity(MediaType mediaType, Resource resource) {
        return toResponseEntity(null, mediaType, resource.contentLength(), resource);
    }

    /**
     * 文件下载或预览
     *
     * @param resource resource
     * @return ResponseEntity
     */
    @SneakyThrows(value = {IOException.class})
    public static ResponseEntity<InputStreamResource> toResponseEntity(Resource resource) {
        var fileName = resource.getFilename();
        return toResponseEntity(fileName, getMediaType(fileName), resource.contentLength(),
                new InputStreamResource(resource.getInputStream()));
    }

    /**
     * 文件下载或预览
     *
     * @param fileName fileName
     * @param in       in
     * @return ResponseEntity
     */
    public static ResponseEntity<InputStreamResource> toResponseEntity(String fileName, long contextLength,
                                                                       InputStream in) {
        var streamResource = new InputStreamResource(in);
        var mediaType = getMediaType(fileName);
        return toResponseEntity(fileName, mediaType, contextLength, streamResource);
    }

    private static <T> ResponseEntity<T> toResponseEntity(String fileName, MediaType mediaType, long contextLength,
                                                          T body) {
        var httpHeaders = new HttpHeaders();
        if (StringUtils.hasLength(fileName)) {
            httpHeaders.setContentDispositionFormData("attachment", obtainAttachFileName(fileName));
        }
        httpHeaders.setContentType(mediaType);
        httpHeaders.setContentLength(contextLength);
        return ResponseEntity.ok().headers(httpHeaders).body(body);
    }

    /**
     * 附件名
     *
     * @param fileName fileName
     * @return fileName
     */
    @SneakyThrows
    private static String obtainAttachFileName(String fileName) {
        var encodeFileName = fileName;
        var agent = getUserAgent(getHttpServletRequest());
        if (Objects.nonNull(agent)) {
            if (agent.contains(EDGE_USER_AGENT_HEADER_VALUE) || agent.contains(MSIE_USER_AGENT_HEADER_VALUE) || agent.contains(TRIDENT_USER_AGENT_HEADER_VALUE)) {// IE
                encodeFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
            } else if (agent.contains(MOZILLA_USER_AGENT_HEADER_VALUE)) {// 火狐,谷歌
                encodeFileName = StringUtils.newStringIso8859_1(StringUtils.getByteUtf8(fileName));
            }
        }
        return encodeFileName;
    }

    /**
     * 获取请求地址
     *
     * @param request request
     * @return ip address
     */
    @Nullable
    public static String getRemoteAddr(HttpServletRequest request) {
        var ip = request.getHeader(X_FORWARDED_FOR_HEADER);
        String[] ipHeaderNames = {"Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"};
        var index = 0;
        while (index < ipHeaderNames.length && (StringUtils.isEmpty(ip) || StringUtils.equalsIgnoreCase(Constants.UNKNOWN, ip))) {
            ip = request.getHeader(ipHeaderNames[index]);
            index++;
        }
        if (StringUtils.isEmpty(ip) || StringUtils.equalsIgnoreCase(Constants.UNKNOWN, ip)) {
            ip = request.getRemoteAddr();
        }
        var ipArray = StringUtils.splitByComma(ip);
        return Arrays.stream(ipArray).filter(item -> !StringUtils.equalsIgnoreCase(item, Constants.UNKNOWN))
                .findFirst().orElse(null);
    }

    /**
     * 获取 user-Agent 信息
     *
     * @param request request
     * @return User-Agent
     */
    @Nullable
    public static String getUserAgent(HttpServletRequest request) {
        return Objects.isNull(request) ? null : request.getHeader(HttpHeaders.USER_AGENT);
    }

    /**
     * 写入Json数据
     *
     * @param response response
     * @param status   status
     * @param data     data
     */
    @SneakyThrows(value = {IOException.class})
    public static void writeJson(HttpServletResponse response, int status, Object data) {
        var value = JsonUtils.serialize(data);
        if (Objects.nonNull(value)) {
            response.setCharacterEncoding(Constants.UTF_8);
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            try (var writer = response.getWriter()) {
                response.setStatus(status);
                writer.write(value);
            }
        }
    }
}
